Repository navigation
feat(backend): centralize amount and currency validation across API and service layers - #139
Open
woahwhattheheck wants to merge 18 commits into
Open
woahwhattheheck wants to merge 18 commits into
woahwhattheheck wants to merge 18 commits into
Conversation
Add a shared currency policy (ISO minor units, min/max, supported codes) used by quote/transfer validators and settlement so preview amounts match executed transfers, unsupported currencies fail before mutation, and zero-decimal corridors like JPY reject fractional sends. Closes RemitFlow#130.
Keep fee components unrounded until their sum reaches the shared currency policy. Compensate for floating-point drift at half-minor-unit boundaries so JPY fees include the flat component without regressing USD cent ties. Add literal boundary regressions and exercise the HTTP quote/transfer path for 30 JPY: fee 1 JPY and recipient amount 0.19 USD. Validation on Node 24.19.0: focused checks 53/53; npm test 289/289.
Malformed JSON and parsed query objects could throw from Number() or an unsupported-currency error label when toString was not callable. Seven actual GET quote / POST transfer cases returned 500 instead of normal validation errors. Accept only numbers and strings for amount conversion, and describe invalid currency types without invoking their conversion properties. Use the shared currency label in policy, quote, and rate errors. Preserve the previous source ceiling, minor-unit, combined-fee, positive-payout, preview/execution, and idempotency behavior. Validation: npm test passed 301/301 with zero failures or skips. The existing currency-policy file passed 45/45; six new regressions fail on sole parent e79ead3 while its previous 39 checks pass. Four changed JavaScript files passed syntax checks and git diff --check passed. Node 24.19.0 reused retained dependencies matching all 76 lockfile package versions; no install or dependency change. Actual local Express HTTP replay on the documented mock FX/Stellar and in-memory store changes all seven malformed JSON/query cases from 500 to 400 with field validation errors and no transfer or retry-key records. Valid transfer 201 and ordinary invalid-amount 400 controls remain. Extended HTTP regressions observe zero settlement calls, transfer/index/audit entries and idempotency records before rejection, and a corrected same-key request then succeeds with the previewed amounts. No live payment or hosted Node 22 CI result is claimed.
Replace the README's all-currencies two-decimal instruction with the implemented JPY and two-decimal currency rules. Document the shared source-amount ceiling and the post-fee, destination-rounded payout check. README only; existing implementation, tests and historical validation remain unchanged. Checked against current source and existing cases; no runtime or maintained suite replayed for this documentation update.
Use exact decimal ratios for fees, net amounts and FX conversion before rounding once to currency minor units. Keep numeric API results and the existing negative-tie direction. Add focused midpoint and preview/transfer regressions to the existing currency policy tests. Native conversion, quote, createTransfer and stored transfer reproduction: 16.04 GBP -> USD: 19.68 -> 19.69 4.14 GBP -> EUR: 4.44 -> 4.45 384 JPY -> EUR: 2.34 -> 2.35 Nine tie/sign controls, twelve fee cases and six existing boundary/rejection controls pass. Real business/store/idempotency/audit modules ran against the repository mock Stellar adapter. No live payment was attempted. Node 24.19.0 with documented default configuration; dotenv loading was a declared no-op and UUID came from the existing runtime. The maintained HTTP file is extended but unexecuted here because locked Express 4/dotenv 16 dependencies are unavailable. No full-suite or hosted CI result is claimed.
Link the focused Node 22 run against the immutable rounding source. All 49 policy cases passed, including the HTTP quote/transfer regressions. Document the command and limit this evidence to the focused test file.
Explicit TRANSFER_FEE_PERCENT=0 or TRANSFER_FEE_FLAT=0 was treated as missing and replaced with the normal 1.5 percent / 0.30 defaults. Parse the fee components with a NaN fallback so individual or complete fee waivers reach the existing canonical quote calculation. Other numeric configuration and decimal rounding are unchanged. Extend the maintained config test file with default, blank, invalid, zero-percent, zero-flat, both-zero and custom-fee quote cases. The same 11 cases on Node 24.19 gave 8 pass / 3 fail against the previous source and 11 pass / 0 fail with this change. Local execution supplied process.env and replaced only dotenv's optional .env loading; configuration, currency and quote modules executed as published. No full HTTP suite, locked dependency run or current CI success is claimed. With both components zero, a 100 USD to EUR quote now has fee 0 and receiveAmount 92.59, instead of fee 1.80 and receiveAmount 90.93. Refs RemitFlow#130
… ci] Integrate d31a56e without replacing the concurrent zero-fee configuration repair at 452a512. The three readback files are byte-identical to the executed candidate; configuration and its tests are preserved. A safe integer number of cents can still change when divided into the existing Number-valued API. Compare the result's decimal representation with the exact rounded integer, rejecting only lossy values. Canonical validation returns the existing numeric-range error instead of throwing. For example, 90071992547409.91 USD no longer silently becomes 90071992547409.9; representable neighbors and whole-number JPY survive. Actual Node 22.16.0 maintained tests in run 37191802959: - Four new boundary groups on parent 2e815f6: 1 pass, 3 fail. - test/currencyPolicy.test.js on d31a56e: 53 pass, 0 fail, 0 skipped. - Includes real Express HTTP 400 and no transfer, idempotency, audit or payment-submission effects. Runtime dependencies installed from the lock. No full-suite or live-payment execution is asserted. The later zero-fee configuration change was not part of that focused run. Evidence artifact 11298779083 SHA256: 1e3d5120092abb08f55b003cc60e8a12a5f5cab65aa6763efd40e944522325bf https://github.com/woahwhattheheck/RemitFlow-Backend/actions/runs/37191802959 Refs RemitFlow#130
Document zero-valued fee components and the focused 60-case run against product 452a512 with locked dependencies. The documented source and selection make the boundary distinct from historical full-suite claims.
Keep the full computed dimensionless FX ratio in getPair and getQuote rather than rounding it like a currency amount. For NGN/USD, the old responses advertised rate 0 beside a positive 6.40 USD receive amount; they now advertise 0.00065. Fee and receive-amount rounding remain unchanged. Focused native Node 22.16.0 comparison on complete source 2e815f6 with these two service changes: test/exchangeRatePrecision.test.js went from 1 passed / 2 failed to 3 passed / 0 failed. The supported 72-pair matrix retains the conversion ratio after JSON serialization; separately compared full pair/quote records preserve every non-rate value. No dependency or test-runner changes. Reproduction with installed project dependencies: node --test test/exchangeRatePrecision.test.js. This local execution used an external inert dotenv.config preload because dotenv was unavailable; real config defaults, services, helpers and static rates executed. No .env loading, application HTTP, live provider, settlement database or full-suite result is claimed. The preload is not committed. Composed on current 2f3aca0, preserving four intervening configuration/currency-policy/documentation commits. Native comparison confirms neither service changed in that interval. Existing PR139 and issue130 remain the contribution path; no new claim or award assertion.
Convert safe integer Numbers directly to decimal ratios while preserving fractional, string and unsafe-integer parsing, exact rounding and readback validation. Include paired production-service measurements and a reproducible benchmark.
A source amount accepted by the configured transfer ceiling can overflow the destination currency's minor-unit range during conversion. With MAX_TRANSFER_AMOUNT=1000000000000, a 1000000000000 USD to NGN quote previously raised a plain RangeError and reached the error handler as an internal error. Translate that conversion-range failure into the existing ApiError 400 contract while preserving other exceptions and the exact rounding implementation. Add one focused regression through the production quote, transfer, error-handler, store, audit and idempotency modules. Rejected overflow makes no settlement call and inserts no records. A corrected 50000 USD request reuses the same key, yields 75768769.23 NGN, and replay retains the same transfer with only one settlement call. Validation: node --test test/quoteOverflow.test.js. Original quote blob 093b0b1 fails the same regression (exit 1); changed quote blob 1fc5319 passes (exit 0, one case). Node v24.19.0, dotenv 16.6.1 and existing runtime uuid 3.4.0. The shipped settlement adapter is local/mock, and the error handler uses a response sink. Scope is service and error-envelope behavior; no Express HTTP, complete-suite or live-settlement result is asserted. Continue the original issue RemitFlow#130 contribution and preserve parent 3581b9f and all earlier precision, fee, rate and throughput work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #130.
Introduces a single currency policy shared by API validators and settlement so preview quotes and executed transfers always agree on precision, supported codes, and overflow bounds.
What
src/utils/currencyPolicy.js— canonical metadata (ISO minor units, min amount, optional max) for every rate-listed currency;canonicalizeAmount/validateTransferPair/roundToCurrency.quoteService/rateService.convertround with the destination currency's minor units (JPY stays whole).Why
Different layers previously accepted different precision or unsupported codes, so a quote could succeed and settlement later fail (or vice versa). One policy table keeps preview and execution identical.
How tested
npm test— 275 passing (includes newtest/currencyPolicy.test.js).Design tradeoffs
SUPPORTED_CURRENCIES(boot-time assert). Adding a corridor means updating both the rate table and metadata in one change.